﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using System.Xml;
using System.IO;
using MetaCollection.Framework.Model;
using MetaCollection.Framework;
using MetaCollection.ContentService.Http;
using System.Globalization;
using System.Collections.Specialized;
using Newtonsoft.Json;
using System.Text;

namespace MetaCollection.ContentService.WebDav
{
	/// <summary>
	/// A WebDav response class which can return XML or object content (text/binary)
	/// </summary>
	public class DavResponse
	{
		/// <summary>
		/// The XML Writer settings allow debug builds to return XML formatted for easy reading when in DEBUG
		/// </summary>
		private static readonly XmlWriterSettings settings = new XmlWriterSettings
		{
#if DEBUG
			Indent = true,
			IndentChars = "\t"
#endif
		};

		/// <summary>
		/// A standard set of Namespaces used for MetaCollection Content Service DAV requests and responses.
		/// </summary>
		public static readonly XAttribute[] Namespaces = new XAttribute[] {
			new XAttribute(XNamespace.Xmlns + "D", "DAV:"), 
			new XAttribute(XNamespace.Xmlns + "mc", "uri://MetaCollection/v2"),
			new XAttribute(XNamespace.Xmlns + "Z", "urn:schemas-microsoft-com:"),
			new XAttribute(XNamespace.Xmlns + "Office", "urn:schemas-microsoft-com:office:office"),
			new XAttribute(XNamespace.Xmlns + "Repl", "http://schemas.microsoft.com/repl/")
		};

		/// <summary>
		/// The associated HTTP Response.
		/// </summary>
		private HttpResponse responseContext;

		private DavContext m_DavContext;


		/// <summary>
		/// Initializes a new instance of the <see cref="DavResponse"/> class.
		/// </summary>
		/// <param name="response">The HTTP response.</param>
		internal DavResponse(HttpResponse response, DavContext context)
		{
			responseContext = response;
			m_DavContext = context;
		}

		/// <summary>
		/// Outputs the text to the response stream.
		/// </summary>
		/// <param name="output">The text to write.</param>
		/// <remarks>Need to implement the Range Header parsing and seek, write as requested or throw a 416 if not valid range</remarks>
		public void OutputText(TextReader output)
		{
			if (output == null)
				throw new ArgumentNullException("output");
			int read = 0;
			char[] buffer = new char[32768];
			while ((read = output.Read(buffer, 0, buffer.Length)) > 0)
			{
				OutputStream.Write(ContentEncoding.GetBytes(buffer), 0, read);
			}
		}

		/// <summary>
		/// Outputs the text to the response stream.
		/// </summary>
		/// <param name="output">The text to write.</param>
		/// <param name="contentType">Type of the content.</param>
		public void OutputText(TextReader output, string contentType)
		{
			ContentType = contentType;
			OutputText(output);
		}

		/// <summary>
		/// Outputs the response text to the response stream.
		/// </summary>
		/// <param name="outputText">The item output.</param>
		/// <remarks>Need to implement the Range Header parsing and seek, write as requested or throw a 416 if not valid range</remarks>
		public void OutputBinary(Stream outputData)
		{
			if (outputData == null)
				throw new ArgumentNullException("outputData");
			int read = 0;
			byte[] buffer = new byte[32768];
			while ((read = outputData.Read(buffer, 0, buffer.Length)) > 0)
			{
				OutputStream.Write(buffer, 0, read);
			}
			outputData.Close();
			outputData.Dispose();
		}

		/// <summary>
		/// Outputs the response text to the response stream.
		/// </summary>
		/// <param name="outputData">The output data.</param>
		/// <param name="contentType">Type of the content.</param>
		/// <remarks>Need to implement the Range Header parsing and seek, write as requested or throw a 416 if not valid range</remarks>
		public void OutputBinary(Stream outputData, string contentType)
		{
			ContentType = contentType;
			OutputBinary(outputData);
		}

		/// <summary>
		/// Outputs XML as the response body of a multi-status WEBDAV response with the correct mimetype and status code.
		/// </summary>
		/// <param name="data">The xml data to output in the response.</param>
		public void OutputXml(IEnumerable<XElement> data)
		{
			if (m_DavContext.Request.RespondWithJson)
			{
				OutputJson(data);
			}
			else
			{
				ContentType = "application/xml";
				StatusCode = (int)Http.SuccessStatus.MultiStatus;
				XStreamingElement stream = new XStreamingElement(XName.Get("multistatus", "DAV:"), DavResponse.Namespaces, data);

				using (XmlWriter writer = XmlWriter.Create(responseContext.Output, settings))
				{
					stream.WriteTo(writer);
					writer.Flush();
				}
			}
		}

		private void OutputJson(IEnumerable<XElement> data)
		{
			ContentType = "application/json";
			StatusCode = (int)Http.SuccessStatus.Ok;
			using (StreamWriter writer = new StreamWriter(OutputStream, Encoding.UTF8))
			{
				bool isFirst = true;
				writer.Write(@"{ ""multistatus"": [");
				foreach (var element in data)
				{
					if (!isFirst)
						writer.Write(",");
					else
						isFirst = false;

					writer.Write(JsonConvert.SerializeXmlNode(element.GetXmlNode()));
				}
				writer.Write("]}");
				writer.Close();
			}
		}

		/// <summary>
		/// Gets or sets the HTTP status code returned to the client.
		/// </summary>
		/// <value>The HTTP status code.</value>
		public int StatusCode
		{
			get { return responseContext.StatusCode; }
			set { responseContext.StatusCode = value; }
		}

		/// <summary>
		/// Gets or sets the HTTP MimeType of the of the request
		/// </summary>
		/// <value>The MimeType of the content.</value>
		public string ContentType
		{
			get { return responseContext.ContentType; }
			set { responseContext.ContentType = value; }
		}

		/// <summary>
		/// Gets or sets the HTTP Charset of the output stream.
		/// </summary>
		/// <value>The HTTP Charset.</value>
		public System.Text.Encoding ContentEncoding
		{
			get { return responseContext.ContentEncoding; }
			set { responseContext.ContentEncoding = value; }
		}

		/// <summary>
		/// Sets the response status code and status string.
		/// </summary>
		/// <param name="context">The HttpContext containing the response</param>
		/// <param name="statusCode">The status code.</param>
		/// <param name="status">The status string.</param>
		public void SetStatus(int statusCode, string status)
		{
			responseContext.StatusDescription = status;
			responseContext.StatusCode = statusCode;
		}

		/// <summary>
		/// Sets Response headers for cachability using last modified information from the item, the item's id and 
		/// version to build an eTag and the cachability value.  By varying the HttpCacheability a response can be
		/// set to cache on the server only, on clients or on any device downstream from the server including the
		/// server itself. <see cref="System.Web.Cacheability"/>
		/// </summary>
		/// <param name="context">The HttpContext containing the request information.</param>
		/// <param name="item">The item to pull last modified and version information from.</param>
		public void SetItemHeaders(IItemInstance item, HttpCacheability cacheability)
		{
			if (item == null)
				throw new ArgumentNullException("item");
			responseContext.Cache.SetCacheability(cacheability);
			responseContext.Cache.SetOmitVaryStar(true);
			responseContext.Cache.SetLastModified(item.LastModified);
			responseContext.Cache.SetETag(String.Format(CultureInfo.InvariantCulture, "{0},{1}", item.Id, item.Version));
		}

		/// <summary>
		/// Sets Response headers for cachability using last modified information. By varying the HttpCacheability
		/// a response can be set to cache on the server only, on clients or on any device downstream from the server
		/// including the server itself. <see cref="System.Web.Cacheability"/>
		/// </summary>
		/// <param name="cacheability">The cacheability.</param>
		/// <param name="lastModified">The last modified.</param>
		public void SetCacheHeaders(HttpCacheability cacheability, DateTime lastModified)
		{
			responseContext.Cache.SetCacheability(cacheability);
			responseContext.Cache.SetOmitVaryStar(true);
			responseContext.Cache.SetLastModified(lastModified);
		}

		/// <summary>
		/// Gets the collection of HTTP Response Headers
		/// </summary>
		public NameValueCollection Headers { get { return responseContext.Headers; } }

		/// <summary>
		/// The Response Output Stream
		/// </summary>
		public Stream OutputStream { get { return responseContext.OutputStream; } }

	}

	public static partial class Extensions
	{
		public static XElement GetXElement(this XNode node)
		{
			if (node == null)
				return null;
			XDocument xDoc = new XDocument();
			using (XmlWriter xmlWriter = xDoc.CreateWriter())
				node.WriteTo(xmlWriter);
			return xDoc.Root;
		}

		public static XmlNode GetXmlNode(this XNode element)
		{
			if (element == null)
				return null;
			using (XmlReader xmlReader = element.CreateReader())
			{
				XmlDocument xmlDoc = new XmlDocument();
				xmlDoc.Load(xmlReader);
				return xmlDoc;
			}
		}
	}
}
